package org.hackystat.sensorbase.db.service;

import org.hackystat.sensorbase.db.DbImplementation;
import org.hackystat.sensorbase.resource.projects.jaxb.Project;
import org.hackystat.sensorbase.resource.projects.jaxb.ProjectSummary;
import org.hackystat.sensorbase.resource.sensordata.jaxb.SensorData;
import org.hackystat.sensorbase.resource.sensordatatypes.jaxb.SensorDataType;
import org.hackystat.sensorbase.resource.users.jaxb.User;
import org.hackystat.sensorbase.server.Server;
import org.restlet.Client;
import org.restlet.data.Form;
import org.restlet.data.MediaType;
import org.restlet.data.Method;
import org.restlet.data.Preference;
import org.restlet.data.Protocol;
import org.restlet.data.Reference;
import org.restlet.data.Request;
import org.restlet.data.Response;
import org.restlet.data.Status;
import org.restlet.resource.Representation;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import javax.xml.datatype.XMLGregorianCalendar;
import java.io.IOException;
import java.io.StringReader;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import static org.hackystat.sensorbase.server.ServerProperties.DB_URL;


public class ServiceImplementation extends DbImplementation {

    public ServiceImplementation(Server server) {
        super(server);
        System.out.println("***ServiceImplementation constructed***");
    }

    private String getData(String query) {
        System.out.println("\nServiceImplementation.getData");
        String result = "Error!";
        Client client = new Client(Protocol.HTTP);
        String dbURL = server.getServerProperties().get(DB_URL);
        System.out.println("Request URL: " + dbURL + query);
        Request request = new Request(Method.GET, dbURL + query);
        Response response = client.handle(request);
        Representation output = response.getEntity();
        try {
            result = output.getText();
            System.out.println("\nresult = " + result);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return result.substring(result.indexOf('>') + 1);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String getSensorDataIndex() {
        return getData("sensordata/");
    }

    @Override
    public String getSensorDataIndex(User user) {
        return getData("sensordata/" + user.getEmail());
    }

    @Override
    public String getSensorDataIndex(User user, String sdtName) {
        return getData("sensordata/" + user.getEmail() + "?sdt=" + sdtName);
    }

    @Override
    public String getSensorDataIndex(List<User> users,
                                     XMLGregorianCalendar startTime, XMLGregorianCalendar endTime,
                                     List<String> uriPatterns, String sdt) {

        String totalData = "<SensorDataIndex>";
        for (User user : users) {
            String partialData = getData("sensordata/" + user.getEmail() + "?sdt=" + sdt + "&lastModStartTime=" + startTime + "&lastModEndTime=" + endTime);
            partialData = partialData.replace("<SensorDataIndex>", "");
            partialData = partialData.replace("</SensorDataIndex>", "");
            partialData = partialData.replace("<SensorDataIndex/>", "");
            totalData += partialData;
        }
        return totalData + "</SensorDataIndex>";
    }

    @Override
    public String getSensorDataIndex(List<User> users,
                                     XMLGregorianCalendar startTime, XMLGregorianCalendar endTime,
                                     List<String> uriPatterns, String sdt, String tool) {
        String totalData = "<SensorDataIndex>";
        for (User user : users) {
            String partialData = getData("sensordata/" + user.getEmail() + "?sdt=" + sdt + "&lastModStartTime=" + startTime + "&lastModEndTime=" + endTime + "&tool=" + tool);
            partialData = partialData.replace("<SensorDataIndex>", "");
            partialData = partialData.replace("</SensorDataIndex>", "");
            partialData = partialData.replace("<SensorDataIndex/>", "");
            totalData += partialData;
        }
        return totalData + "</SensorDataIndex>";
    }

    @Override
    public String getSensorDataIndex(List<User> users,
                                     XMLGregorianCalendar startTime, XMLGregorianCalendar endTime,
                                     List<String> uriPatterns, int startIndex, int maxInstances) {
        String totalData = "<SensorDataIndex>";
        for (User user : users) {
            String partialData = getData("projects/" + user.getEmail() + "/" + uriPatterns + "/sensordata?startTime=" + startTime + "&endTime=" + endTime + "&maxInstances=" + maxInstances);
            partialData = partialData.replace("<SensorDataIndex>", "");
            partialData = partialData.replace("</SensorDataIndex>", "");
            partialData = partialData.replace("<SensorDataIndex/>", "");
            totalData += partialData;
        }
        return totalData + "</SensorDataIndex>";
    }

    @Override
    public String getSensorDataIndexLastMod(User user,
                                            XMLGregorianCalendar lastModStartTime,
                                            XMLGregorianCalendar lastModEndTime) {
        return getData("sensordata/" + user.getEmail() + "?lastModStartTime=" + lastModStartTime + "&lastModEndTime=" + lastModEndTime);
    }

    @Override
    public String getProjectSensorDataSnapshot(List<User> users,
                                               XMLGregorianCalendar startTime, XMLGregorianCalendar endTime,
                                               List<String> uriPatterns, String sdt, String tool) {
        return getData("projects/" + users + "/" + uriPatterns + "/sensordata?startTime=" + startTime + "&endTime=" + endTime + "&sdt=" + sdt + "&tool=" + tool);
    }

    @Override
    public String getSensorData(User user, XMLGregorianCalendar timestamp) {
        System.out.println("\nServiceImplementation.getSensorData");
        return getData("sensordata/" + user.getEmail() + "?timestamp=" + timestamp);
    }

    @Override
    public boolean storeSensorData(SensorData data, String xmlSensorData, String xmlSensorDataRef) {
        System.out.println("\nServiceImplementation.storeSensorData");
        Form form = new Form();
        form.add("xmlSensorData", xmlSensorData);
        form.add("xmlSensorDataRef", xmlSensorDataRef);
        Response res = makeRequest(Method.PUT, "sensordata/" + data.getOwner(), form.getWebRepresentation());
        return res.getStatus().isSuccess();
    }

    @Override
    public void deleteSensorData(User user, XMLGregorianCalendar timestamp) {
        System.out.println("\nServiceImplementation.deleteSensorData(user, timestamp)");
        makeRequest(Method.DELETE, "sensordata/" + user.getEmail() + "/" + timestamp, null);
    }

    @Override
    public void deleteSensorData(User user) {
        System.out.println("\nServiceImplementation.deleteSensorData(user)");
        makeRequest(Method.DELETE, "sensordata/" + user.getEmail(), null);
    }

    @Override
    public String getSensorDataTypeIndex() {
        return getData("sensordatatypes/");
    }

    @Override
    public String getSensorDataType(String sdtName) {
        return getData("sensordatatypes/" + sdtName);
    }

    @Override
    public boolean storeSensorDataType(SensorDataType sdt,
                                       String xmlSensorDataType, String xmlSensorDataTypeRef) {
        Form form = new Form();
        form.add("sdtXml", xmlSensorDataType);
        form.add("sdtXmlRef", xmlSensorDataTypeRef);
        Response res = makeRequest(Method.PUT, "sensordatatypes/" + sdt.getName(), form.getWebRepresentation());
        return res.getStatus().isSuccess();
    }

    @Override
    public void deleteSensorDataType(String sdtName) {
        makeRequest(Method.DELETE, "sensordatatypes/" + sdtName, null);
    }

    @Override
    public String getUserIndex() {
        System.out.println("ServiceImplementation.getUserIndex");
        return getData("users/");
    }

    @Override
    public String getUser(String email) {
        System.out.println("ServiceImplementation.getUser: " + email);
        return getData("users/" + email);
    }

    @Override
    public boolean storeUser(User user, String xmlUser, String xmlUserRef) {
        System.out.println("ServiceImplementation.storeUser");
        System.out.println("user.Email    = " + user.getEmail());
        System.out.println("user.Password = " + user.getPassword());
        System.out.println("user.Role     = " + user.getRole());
        System.out.println("xmlUser       = " + xmlUser);
        System.out.println("xmlUserRef    = " + xmlUserRef);
        System.out.println("");

        String requestString = "users/" + user.getEmail();
        Form form = new Form();
        form.add("email", user.getEmail());
        form.add("password", user.getPassword());
        form.add("role", user.getRole());
        form.add("xmlUser", xmlUser);
        form.add("xmlUserRef", xmlUserRef);
        Response response = makeRequest(Method.POST, requestString, form.getWebRepresentation());
        return response.getStatus().isSuccess();
    }

    @Override
    public void deleteUser(String email) {
        makeRequest(Method.DELETE, "users/" + email, null);
    }

    @Override
    public String getProjectIndex() {
        return getData("projects/");
    }

    @Override
    public String getProject(User owner, String projectName) {
        return getData("projects/" + owner.getEmail() + "/" + projectName);
    }

    @Override
    public boolean storeProject(Project project, String xmlProject, String xmlProjectRef) {
        Form form = new Form();
        form.add("startTime", project.getStartTime().toXMLFormat());
        form.add("endTime", project.getStartTime().toXMLFormat());
        form.add("endTime", project.getEndTime().toXMLFormat());
        form.add("lastMod", project.getLastMod().toXMLFormat());
        form.add("xmlProject", xmlProject);
        form.add("xmlProjectRef", xmlProjectRef);

        Response res = makeRequest(Method.PUT, "projects/" + project.getOwner() + "/" + project.getName(), form.getWebRepresentation());
        return res.getStatus().isSuccess();
    }

    @Override
    public void deleteProject(User owner, String projectName) {
        makeRequest(Method.DELETE, "projects/" + owner.getEmail() + "/" + projectName, null);
    }

    @Override
    public ProjectSummary getProjectSummary(List<User> users, XMLGregorianCalendar startTime, XMLGregorianCalendar endTime, List<String> uriPatterns, String href) {
        System.out.println("\nServiceImplementation.getProjectSummary");
        ProjectSummary res = null;
        StringBuilder requestString = new StringBuilder();
        requestString.append("projects/summary?startTime=").append(startTime)
                .append("&endTime=").append(endTime)
                .append("&userEmailList=").append(toCommaSeparatedUserEmailString(users))
                .append("&uriPatternList=").append(toCommaSeparatedString(uriPatterns))
                .append("&href=").append(href);
        Response response = makeRequest(Method.GET, requestString.toString(), null);
        if (response.getStatus().isSuccess()) {
            Unmarshaller unmarshaller;
            try {
                unmarshaller = JAXBContext.newInstance(org.hackystat.sensorbase.resource.projects.jaxb.ObjectFactory.class).createUnmarshaller();
                res = (ProjectSummary) unmarshaller.unmarshal(new StringReader(response.getEntity().getText()));
            } catch (JAXBException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
        } else {
            System.out.println("Error calling getProjectSummary: " + response.getStatus());
        }
        return res;
    }

    private String toCommaSeparatedString(List<String> stringList) {
        StringBuilder res = new StringBuilder();
        for (String uriPattern : stringList) {
            res.append(uriPattern).append(",");
        }
        return res.substring(0, res.lastIndexOf(","));
    }

    private String toCommaSeparatedUserEmailString(List<User> users) {
        StringBuilder res = new StringBuilder();
        for (User user : users) {
            res.append(user.getEmail()).append(",");
        }
        return res.substring(0, res.lastIndexOf(","));
    }

    @Override
    public boolean compressTables() {
        return Boolean.parseBoolean(getData("db/table/compress"));
    }

    @Override
    public boolean indexTables() {
        return Boolean.parseBoolean(getData("db/table/index"));
    }

    @Override
    public int getRowCount(String table) {
        return Integer.parseInt(getData("db/table/" + table + "/rowcount"));
    }

    @Override
    public Set<String> getTableNames() {
        Set<String> tableNames = new HashSet<String>();
        tableNames.add("SensorData");
        tableNames.add("SensorDataType");
        tableNames.add("HackyUser");
        tableNames.add("Project");
        return tableNames;
    }


    @Override
    public void initialize() {
        // no initialization here as it should happen in the service

    }


    @Override
    public boolean isFreshlyCreated() {
        return false;
    }


    @Override
    public boolean hasSensorData(User user, XMLGregorianCalendar timestamp) {
        return Boolean.parseBoolean(getData("sensordata/" + user.getEmail() + "/" + timestamp));
    }

    /**
     * Does the housekeeping for making HTTP requests to the storage service.
     *
     * @param method        The type of Method.
     * @param requestString A string, such as "users". No preceding slash.
     * @param entity        The representation to be sent with the request, or null if not needed.
     * @return The Response instance returned from the server.
     */
    private Response makeRequest(Method method, String requestString, Representation entity) {
        String dbURL = server.getServerProperties().get(DB_URL);
        Reference reference = new Reference(dbURL + requestString);
        Request request = (entity == null) ? new Request(method, reference)
                : new Request(method, reference, entity);
        request.getClientInfo().getAcceptedMediaTypes()
                .add(new Preference<MediaType>(MediaType.TEXT_XML));
        System.out.println("\nServiceImplementation Tracing: " + method + " "
                + reference);
        if (entity != null) {
            try {
                System.out.println("ENTITY\n");
                System.out.println(entity.getText());
            } catch (Exception e) {
                System.out.println("ServiceImplementation -  Problems with getText() on entity.");
            }
        }
        Client client = new Client(Protocol.HTTP);
        client.setConnectTimeout(5000);
        Response response = client.handle(request);
        Status status = response.getStatus();
        System.out.println("  => " + status.getCode() + " " + status.getDescription());
        return response;
    }
}
